#ifndef TIEC_TREE_COPIER_H
#define TIEC_TREE_COPIER_H

#include "macro.h"
#include "tree.h"

namespace NS_TIEC {
  template<typename Param>
  class TreeCopier : public TreeVisitor<PTR<Tree>, Param> {
  public:
    explicit TreeCopier(const bool copy_info = false): copy_info_(copy_info) { }

    template<typename T, typename = std::enable_if_t<std::is_base_of_v<Tree, T>>>
    PTR<T> copyTree(const PTR<T>& tree, Param param) {
      return std::static_pointer_cast<T>(this->visitTree(tree, param));
    }

    template <typename T, typename = std::enable_if_t<std::is_base_of_v<Tree, T>>>
    List<PTR<T>> copyTrees(const List<PTR<T>>& list, Param param) {
      List<PTR<T>> new_list;
      for (const PTR<T>& tree : list) {
        new_list.add(copyTree(tree, param));
      }
      return new_list;
    }

    PTR<Tree> visitCompilationUnitTree(const PTR<CompilationUnitTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CompilationUnitTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->comment = tree->comment;
      new_tree->ident = copyTree(tree->ident, param);
      new_tree->pkg = copyTree(tree->pkg, param);
      new_tree->annotations = copyTrees<AnnotationTree>(tree->annotations, param);
      new_tree->defs = copyTrees<Tree>(tree->defs, param);
      return new_tree;
    }

    PTR<Tree> visitClassTree(const PTR<ClassTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ClassTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->comment = tree->comment;
      new_tree->ident = tree->ident;
      new_tree->super = copyTree(tree->super, param);
      new_tree->annotations = copyTrees<AnnotationTree>(tree->annotations, param);
      /*for (const PAIR<PTR<IdentifierTree>, PTR<ExpressionTree>>& pair : tree->templates) {
        new_tree->templates.add(PAIR{copyTree(pair.first, param), copyTree(pair.second, param)});
      }*/
      new_tree->members = copyTrees<AnnotatedTree>(tree->members, param);
      new_tree->codes = copyTrees<CodeStatementTree>(tree->codes, param);
      return new_tree;
    }

    PTR<Tree> visitVariableTree(const PTR<VariableTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<VariableTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->comment = tree->comment;
      new_tree->ident = tree->ident;
      new_tree->type_expr = copyTree(tree->type_expr, param);
      new_tree->annotations = copyTrees<AnnotationTree>(tree->annotations, param);
      new_tree->initializer = copyTree(tree->initializer, param);
      new_tree->is_parameter = tree->is_parameter;
      if (copy_info_) {
        new_tree->action = tree->action;
      }
      return new_tree;
    }

    PTR<Tree> visitMethodTree(const PTR<MethodTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<MethodTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->comment = tree->comment;
      new_tree->method_kind = tree->method_kind;
      new_tree->ident = tree->ident;
      new_tree->annotations = copyTrees<AnnotationTree>(tree->annotations, param);
      new_tree->parameters = copyTrees<VariableTree>(tree->parameters, param);
      new_tree->return_type = copyTree(tree->return_type, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitEventTree(const PTR<EventTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<EventTree>();
      new_tree->origin = tree->origin;
      new_tree->modifiers = tree->modifiers;
      new_tree->comment = tree->comment;
      new_tree->ident = tree->ident;
      new_tree->event_obj = copyTree(tree->event_obj, param);
      new_tree->annotations = copyTrees<AnnotationTree>(tree->annotations, param);
      new_tree->parameters = copyTrees<VariableTree>(tree->parameters, param);
      new_tree->return_type = copyTree(tree->return_type, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitAnnotationTree(const PTR<AnnotationTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<AnnotationTree>();
      new_tree->origin = tree->origin;
      new_tree->name = tree->name;
      new_tree->args = copyTrees<ExpressionTree>(tree->args, param);
      return new_tree;
    }

    PTR<Tree> visitCodeStatementTree(const PTR<CodeStatementTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CodeStatementTree>();
      new_tree->origin = tree->origin;
      new_tree->code_kind = tree->code_kind;
      new_tree->tag = tree->tag;
      new_tree->lines = copyTrees<CodeSegmentTree>(tree->lines, param);
      return new_tree;
    }

    PTR<Tree> visitCodeExpressionTree(const PTR<CodeExpressionTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CodeExpressionTree>();
      new_tree->origin = tree->origin;
      new_tree->code_kind = tree->code_kind;
      new_tree->tag = tree->tag;
      new_tree->lines = copyTrees<CodeSegmentTree>(tree->lines, param);
      return new_tree;
    }

    PTR<Tree> visitCodeSegmentTree(const PTR<CodeSegmentTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CodeSegmentTree>();
      new_tree->origin = tree->origin;
      new_tree->base_indent = tree->base_indent;
      new_tree->trees = copyTrees<ExpressionTree>(tree->trees, param);
      return new_tree;
    }

    PTR<Tree> visitBlockTree(const PTR<BlockTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<BlockTree>();
      new_tree->origin = tree->origin;
      new_tree->statements = copyTrees<StatementTree>(tree->statements, param);
      return new_tree;
    }

    PTR<Tree> visitExpressionStatementTree(const PTR<ExpressionStatementTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ExpressionStatementTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      return new_tree;
    }

    PTR<Tree> visitIfTree(const PTR<IfTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<IfTree>();
      new_tree->origin = tree->origin;
      new_tree->condition = copyTree(tree->condition, param);
      new_tree->then_part = copyTree(tree->then_part, param);
      new_tree->else_part = copyTree(tree->else_part, param);
      return new_tree;
    }

    PTR<Tree> visitSwitchTree(const PTR<SwitchTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<SwitchTree>();
      new_tree->origin = tree->origin;
      new_tree->selector = copyTree(tree->selector, param);
      new_tree->cases = copyTrees<CaseTree>(tree->cases, param);
      new_tree->default_case = copyTree(tree->default_case, param);
      return new_tree;
    }

    PTR<Tree> visitCaseTree(const PTR<CaseTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CaseTree>();
      new_tree->origin = tree->origin;
      new_tree->conditions = copyTrees<ExpressionTree>(tree->conditions, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitForLoopTree(const PTR<ForLoopTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ForLoopTree>();
      new_tree->origin = tree->origin;
      new_tree->target = copyTree(tree->target, param);
      new_tree->start = copyTree(tree->start, param);
      new_tree->end = copyTree(tree->end, param);
      new_tree->step = copyTree(tree->step, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitForEachTree(const PTR<ForEachTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ForEachTree>();
      new_tree->origin = tree->origin;
      new_tree->source = copyTree(tree->source, param);
      new_tree->var1 = copyTree(tree->var1, param);
      new_tree->var2 = copyTree(tree->var2, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitWhileLoopTree(const PTR<WhileLoopTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<WhileLoopTree>();
      new_tree->origin = tree->origin;
      new_tree->condition = copyTree(tree->condition, param);
      new_tree->body = copyTree(tree->body, param);
      return new_tree;
    }

    PTR<Tree> visitLiteralTree(const PTR<LiteralTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<LiteralTree>();
      new_tree->origin = tree->origin;
      new_tree->value = tree->value;
      return new_tree;
    }

    PTR<Tree> visitArrayTypeTree(const PTR<ArrayTypeTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ArrayTypeTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      new_tree->size = copyTree(tree->size, param);
      return new_tree;
    }

    PTR<Tree> visitTemplateTypeTree(const PTR<TemplateTypeTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<TemplateTypeTree>();
      new_tree->origin = tree->origin;
      new_tree->class_name = copyTree(tree->class_name, param);
      new_tree->args = copyTrees<ExpressionTree>(tree->args, param);
      return new_tree;
    }

    PTR<Tree> visitIdentifierTree(const PTR<IdentifierTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<IdentifierTree>();
      new_tree->origin = tree->origin;
      new_tree->name = tree->name;
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
      }
      return new_tree;
    }

    PTR<Tree> visitFieldAccessTree(const PTR<FieldAccessTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<FieldAccessTree>();
      new_tree->origin = tree->origin;
      new_tree->prefix = copyTree(tree->prefix, param);
      new_tree->ident = copyTree(tree->ident, param);
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
      }
      return new_tree;
    }

    PTR<Tree> visitMethodInvokeTree(const PTR<MethodInvokeTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<MethodInvokeTree>();
      new_tree->origin = tree->origin;
      new_tree->method = copyTree(tree->method, param);
      new_tree->args = copyTrees<ExpressionTree>(tree->args, param);
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
        new_tree->arg_actions = tree->arg_actions;
      }
      return new_tree;
    }

    PTR<Tree> visitNewClassTree(const PTR<NewClassTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<NewClassTree>();
      new_tree->origin = tree->origin;
      new_tree->class_name = copyTree(tree->class_name, param);
      return new_tree;
    }

    PTR<Tree> visitNewArrayTree(const PTR<NewArrayTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<NewArrayTree>();
      new_tree->origin = tree->origin;
      new_tree->elements = copyTrees<ExpressionTree>(tree->elements, param);
      return new_tree;
    }

    PTR<Tree> visitArrayAccessTree(const PTR<ArrayAccessTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ArrayAccessTree>();
      new_tree->origin = tree->origin;
      new_tree->selected = copyTree(tree->selected, param);
      new_tree->index = copyTree(tree->index, param);
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
      }
      return new_tree;
    }

    PTR<Tree> visitBreakTree(const PTR<BreakTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<BreakTree>();
      new_tree->origin = tree->origin;
      return new_tree;
    }

    PTR<Tree> visitContinueTree(const PTR<ContinueTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ContinueTree>();
      new_tree->origin = tree->origin;
      return new_tree;
    }

    PTR<Tree> visitReturnTree(const PTR<ReturnTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ReturnTree>();
      new_tree->origin = tree->origin;
      new_tree->value = copyTree(tree->value, param);
      if (copy_info_) {
        new_tree->action = tree->action;
      }
      return new_tree;
    }

    PTR<Tree> visitSubscribeEventTree(const PTR<SubscribeEventTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<SubscribeEventTree>();
      new_tree->origin = tree->origin;
      new_tree->vars = copyTrees<IdentifierTree>(tree->vars, param);
      return new_tree;
    }

    PTR<Tree> visitTypeCastTree(const PTR<TypeCastTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<TypeCastTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      new_tree->type_expr = copyTree(tree->type_expr, param);
      return new_tree;
    }

    PTR<Tree> visitAssignTree(const PTR<AssignTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<AssignTree>();
      new_tree->origin = tree->origin;
      new_tree->left = copyTree(tree->left, param);
      new_tree->right = copyTree(tree->right, param);
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
        new_tree->action = tree->action;
      }
      return new_tree;
    }

    PTR<Tree> visitExchangeTree(const PTR<ExchangeTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ExchangeTree>();
      new_tree->origin = tree->origin;
      new_tree->left = copyTree(tree->left, param);
      new_tree->right = copyTree(tree->right, param);
      if (copy_info_) {
        new_tree->lhs_symbol = tree->lhs_symbol;
        new_tree->rhs_symbol = tree->rhs_symbol;
      }
      return new_tree;
    }

    PTR<Tree> visitInstanceOfTree(const PTR<InstanceOfTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<InstanceOfTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      new_tree->type_expr = copyTree(tree->type_expr, param);
      return new_tree;
    }

    PTR<Tree> visitMultiExprTree(const PTR<MultiExprTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<MultiExprTree>();
      new_tree->origin = tree->origin;
      new_tree->expressions = copyTrees<ExpressionTree>(tree->expressions, param);
      return new_tree;
    }

    PTR<Tree> visitAwaitTree(const PTR<AwaitTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<AwaitTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      return new_tree;
    }

    PTR<Tree> visitParensTree(const PTR<ParensTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ParensTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      return new_tree;
    }

    PTR<Tree> visitUnaryTree(const PTR<UnaryTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<UnaryTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      new_tree->op_token = tree->op_token;
      return new_tree;
    }

    PTR<Tree> visitBinaryTree(const PTR<BinaryTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<BinaryTree>();
      new_tree->origin = tree->origin;
      new_tree->left = copyTree(tree->left, param);
      new_tree->right = copyTree(tree->right, param);
      new_tree->op_token = tree->op_token;
      if (copy_info_) {
        new_tree->symbol = tree->symbol;
      }
      return new_tree;
    }

    PTR<Tree> visitCodeTextTree(const PTR<CodeTextTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CodeTextTree>();
      new_tree->origin = tree->origin;
      new_tree->content = tree->content;
      return new_tree;
    }

    PTR<Tree> visitCodeRefTree(const PTR<CodeRefTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<CodeRefTree>();
      new_tree->origin = tree->origin;
      new_tree->expr = copyTree(tree->expr, param);
      new_tree->ref_kind = tree->ref_kind;
      return new_tree;
    }

    PTR<Tree> visitErroneousTree(const PTR<ErroneousTree>& tree, Param param) override {
      auto new_tree = MAKE_PTR<ErroneousTree>();
      new_tree->origin = tree->origin;
      new_tree->tokens = tree->tokens;
      new_tree->msg = tree->msg;
      return new_tree;
    }
  protected:
    bool copy_info_ {false};
  };
} // namespace NS_TIEC

#endif // TIEC_TREE_COPIER_H
